package com.yangrd.helper.crudhelper.core.support;

import com.github.wenhao.jpa.PredicateBuilder;
import com.github.wenhao.jpa.Specifications;
import com.yangrd.helper.crudhelper.common.util.ReflectionUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.logging.log4j.util.Strings;
import org.springframework.data.jpa.domain.Specification;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.text.ParseException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * description: DomainSpecSupport
 *
 * @author: yangrd
 * @date: 2020/9/14
 * @version: 1.0
 */
public class DomainSpecSupport {

    private static final String DATE_PATTERN = "yyyy-MM-dd";


    public static Specification<?> toSpec(Class<?> tClass, HttpServletRequest request) {
        Map<String, String[]> parameterMap = request.getParameterMap();
        PredicateBuilder<?> builder = Specifications.and();
        Map<String, Field> fieldMap = ReflectionUtils.listAllField(tClass).stream().collect(Collectors.toMap(Field::getName, Function.identity()));
        parameterMap.forEach((name, values) -> {
            name = name.replaceAll("\\$", ".");
            String value = values[0];
            if (isFieldPresent(name, fieldMap) && Strings.isNotBlank(value)) {
                Field field = getField(name, fieldMap);
                Class<?> fieldType = field.getType();
                if (fieldType.equals(String.class)) {
                    builder.like(name, "%" + value + "%");
                } else if (fieldType.equals(Boolean.class)) {
                    builder.eq(name, value.equals("true"));
                } else if (fieldType.equals(Date.class)) {
                    builder.between(name, getDate(value.split(",")[0]), getDate(value.split(",")[1]));
                } else if (fieldType.equals(LocalDateTime.class)) {
                    builder.between(name, getDate(value.split(",")[0]).toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(), getDate(value.split(",")[1]).toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime());
                } else if (fieldType.equals(LocalDate.class)) {
                    builder.between(name, getDate(value.split(",")[0]).toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), getDate(value.split(",")[1]).toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
                } else if (Number.class.isAssignableFrom(fieldType)) {
                    builder.eq(name, Integer.valueOf(value));
                } else if (Enum.class.isAssignableFrom(fieldType)) {
                    builder.eq(name, Enum.valueOf((Class<Enum>) fieldType, values[0]));
                }
            }
        });
        return builder.build();
    }

    private static Date getDate(String value1) {
        try {
            return DateUtils.parseDate(value1, DATE_PATTERN);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }

    private static boolean isFieldPresent(String fieldName, Map<String, Field> fieldMap) {
        if (fieldName.contains(".")) {
            int i = fieldName.indexOf(".");
            String leftName = fieldName.substring(0, i);
            String rightName = fieldName.substring(i + 1);
            if (fieldMap.containsKey(leftName)) {
                Class<?> aClass = fieldMap.get(leftName).getType();
                if (Collection.class.isAssignableFrom(aClass)) {
                    Field field = fieldMap.get(leftName);
                    Type genericType = field.getGenericType();
                    if (genericType instanceof ParameterizedType) {
                        Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments();
                        aClass = (Class<?>) actualTypeArguments[0];
                    }
                }
                Map<String, Field> childFieldMap = ReflectionUtils.listAllField(aClass).stream().collect(Collectors.toMap(Field::getName, Function.identity()));
                return isFieldPresent(rightName, childFieldMap);
            }
        }
        return fieldMap.containsKey(fieldName);
    }


    private static Field getField(String fieldName, Map<String, Field> fieldMap) {
        if (fieldName.contains(".")) {
            int i = fieldName.indexOf(".");
            String leftName = fieldName.substring(0, i);
            String rightName = fieldName.substring(i + 1);
            if (fieldMap.containsKey(leftName)) {
                Class<?> aClass = fieldMap.get(leftName).getType();
                if (Collection.class.isAssignableFrom(aClass)) {
                    Field field = fieldMap.get(leftName);
                    Type genericType = field.getGenericType();
                    if (genericType instanceof ParameterizedType) {
                        Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments();
                        aClass = (Class<?>) actualTypeArguments[0];
                    }
                }
                Map<String, Field> childFieldMap = ReflectionUtils.listAllField(aClass).stream().collect(Collectors.toMap(Field::getName, Function.identity()));
                return getField(rightName, childFieldMap);
            }
        }
        return fieldMap.get(fieldName);
    }

}
